import time as time_package
from copy import deepcopy
from math import isinf
from typing import Tuple, Dict, Union, Optional

from model.action import Action
from model.cptem import CPTEM
from model.cptemfactory import CPTEMFactory
from model.ec import NetworkEvolutionCondition
from model.log import Log
from model.networkstate import NetworkState
from model.networktrace import NetworkTrace
from model.topo import Topo
from model.transition import Transition
from model.transtionqueue import TransitionsQueue


class NPTEM:
    def __init__(self, cptems: Optional[Dict[Union[str, Tuple[str, str]], CPTEM]] = None,
                ec:NetworkEvolutionCondition = None,
                ) -> None:
        self.cptems = cptems
        self.ec = ec 
        self.time = 0
        self.tq = TransitionsQueue()
        self.logger_state = {}
        self.state = NetworkState()
        self.trace = NetworkTrace()
        # self.state_trace = []
        # self.act_trace = []
    
    def set_logger_state(self) -> None:
        s = {}
        for component, cptem in self.cptems.items():
            if cptem.cptem_entity_type() == "service edge":
                paths = cptem.attr["service_edge"]
                string = ""
                for pairs, paths_dict in paths.items():
                    string += pairs[0] + "->" + pairs[1] + " : "
                    for path, weight in paths_dict.items():
                        if not isinf(weight):
                            string += str(path) + " \\n "
                s[component] = cptem.get_current_state().name + " \\n " + string
            elif cptem.cptem_entity_type() == "application":
                paths = cptem.attr["app_path"]
                string = ""
                for path, weight in paths.items():
                    if not isinf(weight):
                        string += str(path) + " \\n "
                s[component] = cptem.get_current_state().name + " \\n " + string
            else:
                s[component] = cptem.get_current_state().name
        self.logger_state = s
    
    def get_logger_state(self) -> Dict[Union[str, Tuple[str, str]], str]:
        return self.logger_state

    def set_state(self):
        self.state.set(self.cptems)

    def get_state(self):
        return self.state

    def update_trace(self, act: Optional[Action]):
        self.trace.append(self.time, deepcopy(self.state), act)




    # def trace_add_logger_state(self):
    #     self.state_trace.append((self.time,self.get_logger_state()))
    #
    # def trace_add_logger_act(self, act:Action):
    #     self.act_trace.append((self.time,act))





    def run(self, time_bound: float, mode: str = "deterministic", log_mode: Optional[str] = "plantuml"):

        if log_mode is not None:
            logger = Log("%s model.puml" % time_package.strftime("%Y%m%d-%H%M%S"), mode = log_mode)
        else:
            logger = None
        if (mode != "deterministic") and (mode != "stochastic"):
            raise ValueError("mode must be 'deterministic' or 'stochastic'")
        while self.time < time_bound:
            if self.ec.empty():
                # 找到自动触发的迁移
                component_transitions_delay_dict = self.find_enable_transitions_for_nptem(None, mode, logger)
                # 令自动触发的迁移使能
                self.fire_action_for_nptem(component_transitions_delay_dict, logger,True)

                self.set_state()
                self.update_trace(None)
            else:
                (d, act) = self.ec.get_ec()
                cptem = self.cptems.get(act.entity)
                if cptem is None:
                    raise ValueError("no cptem for entity: %s" % act.entity)
                self.time += d
                if cptem.firing is not None:

                    # 动作完成，设置对应的构件cptem状态


                    if logger is not None:
                        self.set_logger_state()
                        self.log_state_info(log_mode, logger)
                        # self.trace_add_logger_state()
                        # self.trace_add_logger_act(cptem.get_firing_transition().act)

                    self.set_state()
                    self.update_trace(cptem.get_firing_transition().act)

                    cptem.fired_transition_update_cptem(act)
                    if logger is not None:
                        self.log_transition_info(act.entity, "[", act, "finished", self.time, log_mode, logger)
                    # if d != 0:
                    #     self.log_transition_info(act.entity, "[", act,"finished", self.time, log_mode, logger)
                    # else:
                    #     self.log_transition_info("[", act.entity, act,"None", self.time, log_mode, logger)



                    # 当前动作是否取消正在fire的迁移
                    if not self.tq.empty():
                        duration_list_transition_queue = self.tq.time_stamp()
                        for transition_in_queue_duration in duration_list_transition_queue:
                            if d < transition_in_queue_duration:
                                # 考虑是否触发inhibition
                                _, component, transition = self.tq.get_transitions()
                                flag = transition.inhibition(act, self.cptems[component].attr)
                                if flag:
                                    self.cptems[component].inhibit_transition()
                                    # 演化条件队列的也要取出来。
                                    self.ec.del_ec(transition_in_queue_duration, transition.act.act_name, component)
                                    if logger is not None:
                                        self.log_transition_info(component, act.entity,
                                                                Action(component, "cancel", "active"),
                                                                "inhibition", self.time, log_mode, logger)

                    # 引发别的构件迁移的分支
                    component_transitions_delay_dict = self.find_enable_transitions_for_nptem(act, mode, logger)
                    if len(component_transitions_delay_dict) != 0 :
                        # 打印引发的迁移
                        for component, (t, d) in component_transitions_delay_dict.items():
                            if act.entity != component:
                                # self.log_transition_info(act.entity, component, act.act_name, "fire", self.time, log_mode, logger)
                                if logger is not None:
                                    self.log_transition_info(act.entity, component, act, "None", self.time, log_mode, logger)
                        # 令触发的迁移使能,同时打印被引发的迁移
                        self.fire_action_for_nptem(component_transitions_delay_dict, logger,False)

                    # 引发自身迁移的分支
                    component_transitions_delay_dict = {}
                    transitions = cptem.find_enable_transitions(None)
                    if transitions is not None:
                        for t in transitions:
                            if mode == "deterministic":
                                if log_mode == "plantuml":
                                    print("%s: %s" % ( act.entity, t.name))
                                    d = t.delay.input_delay()
                                else:
                                    if logger is not None:
                                        logger.info("%s: %s" % ( act.entity, t.name))
                                        d = t.delay.input_delay()
                            elif mode == "stochastic":
                                d = t.delay.generate_delay()
                            component_transitions_delay_dict[act.entity] = (t, d)
                        self.fire_action_for_nptem(component_transitions_delay_dict, logger, False)
        if logger is not None:
            if logger.mode == "plantuml":
                logger.logger.info("@enduml")
        return self.trace

    def find_enable_transitions_for_nptem(self, act: Optional[Action], mode:str = "deterministic",
                                          logger: Optional[Log] = None) ->\
            Optional[Dict[Union[str, Tuple[str, str]], Tuple[Transition, float]]]:
        """
        Find all enabled transitions for all components in the NPTEM.

        Parameters
        ----------
        act : Optional[Action]
            The action that has just been fired.

        mode : str
            The mode of the simulation.

        logger : Optional[Log]
            The logger to use.

        Returns
        -------
        Optional[Dict[Union[str, Tuple[str, str]], Tuple[Transition, float]]]
            A dictionary mapping each component to its enabled transition and the time at which the transition will be fired.

        Raises
        ------
        ValueError
            If cptems is None or if any of the transitions are None.

        """
        component_transitions_delay_dict = {}
        if self.cptems is None:
            raise ValueError("cptems is None")
        for component, cptem in self.cptems.items():
            if cptem is None:
                # raise ValueError("cptem is None")
                continue
            if act is not None:
                # 不用重复触发自己
                if cptem.entity == act.entity:
                    continue
            ts = cptem.find_enable_transitions(act)
            if ts is None:
                raise ValueError("ts is None")
            for t in ts:
                if t is None:
                    raise ValueError("t is None")
                if mode == "deterministic":
                    if logger is not None:
                        if logger.mode == "plantuml":
                            print("%s: %s" % ( component, t.name))
                            d = t.delay.input_delay()
                        else:
                            logger.info("%s: %s" % ( component, t.name))
                            d = t.delay.input_delay()
                    else:
                        d = t.delay.input_delay()
                elif mode == "stochastic":
                    d = t.delay.generate_delay()
                else:

                    raise ValueError("mode must be 'deterministic' or 'stochastic'")
                component_transitions_delay_dict[component] = (t, d)
        return component_transitions_delay_dict


    def fire_action_for_nptem(self, component_transitions_delay_dict:
                              Optional[Dict[Union[str, Tuple[str, str]], Tuple[Transition, float]]] = None,
                              logger: Optional[Log] = None,
                              initial:bool = False
                             ) -> None:

        for component in component_transitions_delay_dict.keys():
            t, d = component_transitions_delay_dict[component]
            self.ec.put_ec((d, t.act))
            self.cptems[component].firing_transition(t)
            # 如果激活的迁移可以被中断，则放入队列中
            if t.inhibition is not None:
                self.tq.put_transitions((d, component, t))
            if logger is not None:
                # 打印信息部分
                if initial:
                    s = "activate " + str(t.act.entity)
                    s = s.replace("'", "").replace("(", '''"(''').replace(")", ''')"''')
                    logger.info(s)
                else:
                    self.log_transition_info("[", t.act.entity, t.act, "fire", self.time, logger.mode, logger)
                # if d != 0:
                #     self.log_transition_info("[", t.act.entity, t.act,"fire", self.time, logger.mode, logger)

            # if logger.mode == "plantuml":
            #     s = "%s -> %s : %s" % (t.act.entity, t.act.entity, t.act.act_name)
            #     logger.info(s.replace("'", "").replace("(",'''"(''').replace(")",''')"'''))
            #     # logger.info(s.replace("'", ""))
            # else:
            #     logger.info("Time:%-15.3f Component:%-12s Action:%-15s fire" % (self.time, t.act.entity, t.act.act_name))

    def log_state_info(self,log_mode="plantuml", logger=None):
        i = 0
        if log_mode == "plantuml":
            for component, state in self.get_logger_state().items():
                s = "hnote over %s : %s" % (component, state)
                if i != 0:
                    s = "/ " + s
                i += 1
                s = s.replace("'", "").replace("(", '''"(''').replace(")", ''')"''')
                logger.info(s)

    @staticmethod
    def log_transition_info(source: str, target: str, act: Action, activate: str, time: float, log_mode: str, logger: Log) -> None:
        if log_mode is None or logger is None:
            raise ValueError("log_mode and logger cannot be None")
        if log_mode not in ["plantuml", "log"]:
            raise ValueError("log_mode must be 'plantuml' or 'log'")
        if log_mode == "plantuml":

            s = "%s->%s : (%15.3f:%s)" % (source, target, time, act.act_name)
            logger.info(s.replace("'", "").replace("(", '''"(''').replace(")", ''')"'''))
            if activate == "fire":
                s = "activate " + str(target)
                logger.info(s.replace("'", "").replace("(", '''"(''').replace(")", ''')"'''))
            elif activate == "finished":
                s = "deactivate " + str(source)
                logger.info(s.replace("'", "").replace("(", '''"(''').replace(")", ''')"'''))
            elif activate == "inhibition":
                s = "destroy " + str(target)
                logger.info(s.replace("'", "").replace("(", '''"(''').replace(")", ''')"'''))
            elif activate == "None":
                pass
        else:
            logger.info("Time:%-15.3f Component:%-12s Action:%-15s finished" % (time, target, act.act_name))

    @staticmethod
    def mlag():
        topo = Topo().mlag()
        cptem_factory = CPTEMFactory(topo)
        cptems = cptem_factory.gen_cptem_mapper()
        ec = NetworkEvolutionCondition()
        nptem = NPTEM(cptems=cptems, ec=ec)
        # trace = model.run(mode="stochastic", log_mode=None)
        # trace = model.run(mode="stochastic", log_mode="plantuml")
        # trace = model.run(mode="deterministic", log_mode="plantuml")
        return nptem

    @staticmethod
    def mlag_run():
        nptem = NPTEM.mlag()
        time_bound = 1000 * 365 * 24 * 60 * 60
        trace = nptem.run(time_bound, mode="stochastic", log_mode=None)
        return trace


if __name__ == "__main__":
    # topo_info = Topo().mlag()
    # cptem = CPTEMFactory.gen_cptem_for_service_node("s2",topo_info)
    # cptem.to_dot("s2.dot")
    import timeit
    tr = NPTEM.mlag_run()
    t = timeit.timeit(NPTEM.mlag_run, number=1)
    print(t)
